package com.walker.taogong.hibernate;

import java.sql.Connection;

import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.stat.CollectionStatistics;
import org.hibernate.stat.EntityStatistics;
import org.hibernate.stat.Statistics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Utility class for dealing with Hibernate sessions and session factories.
 * 
 * @author Alex Wei
 * 
 */
public class HibernateUtil
{
    private static Logger log = LoggerFactory.getLogger(HibernateUtil.class);
    
    /** The session factory is a singleton per application. */
    private static SessionFactory sessionFactory;

    /** database dialect */
    private static String dialect;
    
    private static SessionFactoryProvider sessionFactoryProvider;
    
//    private static final SchemaManagerCache SCHEMA_MANAGER_CACHE = new SchemaManagerCache();
    
    /** This class shall not be instantiated. */
    private HibernateUtil()
    {
    }
    
    /** Returns the singleton session factory. */
    public static synchronized SessionFactory getSessionFactory()
    {
        if (sessionFactory == null || sessionFactoryProvider.isUpdated())
        {
            log.info("init");
            sessionFactory = sessionFactoryProvider.getSessionFactory();
            dialect = sessionFactoryProvider.getDialect();
        }
        return sessionFactory;
    }
    
    public static synchronized void closeSessionFactory()
    {        
        if (sessionFactory != null)
        {
            SessionFactory sf = HibernateUtil.getSessionFactory();
            Statistics stats = sf.getStatistics();
            if(stats.isStatisticsEnabled())
            {
                for(Object m : sf.getAllClassMetadata().keySet())
                {
                    String hibernateQualifier = m.toString();
                    EntityStatistics entityStats = 
                        stats.getEntityStatistics(hibernateQualifier); // or Sale.class.getName();
                    CollectionStatistics cStats = 
                        stats.getCollectionStatistics(hibernateQualifier);
                    long eFetchCount = entityStats.getFetchCount();
                    long cFetchCount = cStats.getFetchCount();
                    
                    if(eFetchCount > 0 || cFetchCount > 0)
                    {
                        log.info("Stats for " + hibernateQualifier);
                        if(entityStats.getFetchCount() > 0)
                            log.info(entityStats.toString());                    
                    
                        if(cStats.getFetchCount() > 0)
                            log.info(cStats.toString());
                    }
                    
                }
            }
            sf.close();
        }
    }

    /**
     * Returns the current session. A session is created automatically if there
     * is none.
     * 
     * @return Hibernate session
     */
    public static Session getCurrentSession()
    {
        SessionFactory sf = getSessionFactory();
        Session session = sf.getCurrentSession();
        if (!session.isOpen())
        {
            session = sf.openSession();
        }
        Transaction transaction = session.getTransaction();
        if (!transaction.isActive())
        {
            session.beginTransaction();
        }
        return session;
    }
    
    public static boolean areThereOpenSessionsLeft()
    {
        return getSessionFactory().getStatistics().getSessionOpenCount()
        != getSessionFactory().getStatistics().getSessionCloseCount();
    }

    public static Session getOpenSession()
    {
        SessionFactory sf = getSessionFactory();
        Session session = sf.getCurrentSession();
        if (!session.isOpen())
        {
            return null;
        }
        return session;
    }
    /**
     * Returns the underlying JDBC connection for the current Hibernate session.
     * 
     * @return JDBC connection
     */
    @SuppressWarnings("deprecation")
    public static Connection getConnection()
    {
        return getCurrentSession().connection();
    }

    /**
     * Commits a given session and opens a new one.
     * 
     * @param session
     *            the session to be committed and closed
     * @return a new session with an open transaction
     */
    public static Session commitAndBegin(Session session)
    {
        session.getTransaction().commit();
        Session newSession = getSessionFactory().openSession();
        newSession.beginTransaction();
        return newSession;
    }

    /**
     * Commits the current session. Prerequisite: The current session has an
     * open transaction.
     */
    public static void commit()
    {
        Session session = getSessionFactory().getCurrentSession();
        if (session.isOpen() && session.getTransaction().isActive())
        {
            session.getTransaction().commit();
        }
        else if (session.isOpen())
        {
            session.close();
        }
    }
    

    /**
     * Rolls back the current session. Prerequisite: The current session has an
     * open transaction.
     */
    public static void rollback()
    {
        Session session = getSessionFactory().getCurrentSession();
        if (session.isOpen() && session.getTransaction().isActive())
        {
            session.getTransaction().rollback();
        }
        else if (session.isOpen())
        {
            session.close();
        }
    }

    
//    /**
//     * Returns a SchemaManager.
//     * 
//     * @return SchemaManager
//     */
//    public static SchemaManager getSchemaManager()
//    {
//        return SCHEMA_MANAGER_CACHE.getCurrentSchemaManager();
//    }
//    
//    /**
//     * Set SchemaConfigurationRegister. This method should be used only by 
//     * SchemaConfigurationRegisterService.
//     * 
//     * @param configurationRegister
//     */
//    public static synchronized void setConfigurationRegister(
//            SchemaConfigurationRegister configurationRegister)
//    {
//        SCHEMA_MANAGER_CACHE.setConfigurationRegister(configurationRegister);
//    }

    /**
     * Obtains a global write lock. The lock is released by the next commit.
     * This can be used to avoid deadlocks caused by page-level locking used by
     * SQL Server.
     * <p>
     * For other database dialects, this is a no-op.
     * 
     * @param session
     */
    public static void writeLock(Session session)
    {
        if ("org.hibernate.dialect.SQLServerDialect".equals(dialect))
        {
            String sql = "update nds.link_lock set seq_num = seq_num+1";
            session.createSQLQuery(sql).executeUpdate();
        }
    }

    /**
     * Obtains a global read lock. The lock is released by the next commit. This
     * can be used to avoid deadlocks caused by page-level locking used by SQL
     * Server.
     * <p>
     * For other database dialects, this is a no-op.
     * 
     * @param session
     */
    public static void readLock(Session session)
    {
        if ("org.hibernate.dialect.SQLServerDialect".equals(dialect))
        {
            String sql = "select seq_num from nds.link_lock";
            session.createSQLQuery(sql).uniqueResult();
        }
    }

    /**
     * In some cases, queries can be accelerated significantly by invoking this
     * method before running this query.
     * 
     * Currently, this method only has an effect when running under PostgreSQL.
     * 
     * @param session
     */
    public static void optimize(Session session)
    {
        if ("org.hibernate.dialect.PostgreSQLDialect".equals(dialect))
        {
            SQLQuery query = session.createSQLQuery("ANALYZE");
            query.executeUpdate();
        }
    }

    /**
     * Initializes this singleton helper class. Only to be used by
     * HibernateInitializer component
     * @param sf   session factory
     * @param dialectName Hibernate SQL dialect
     */
    static synchronized void initialize(SessionFactory sf, String dialectName)
    {
        log.info("initialize");
        sessionFactory = sf;
        dialect = dialectName;
    }
    
    static synchronized void setSessionFactoryProvider(SessionFactoryProvider provider)
    {
        sessionFactoryProvider = provider;
    }
}
